﻿using Extented.UI.Core.Utils;
using Infrastructure.Utils.Helpers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Data;

namespace Extented.UI.Core.Helpers
{
    public static class RenderTriggerHelper
    {
        public static readonly ReflectionHelper Helper = new ReflectionHelper();
        static readonly Type DefaultValueConverter;
        static readonly Type SystemConvertConverter;
        static RenderTriggerHelper()
        {
            DefaultValueConverter = typeof(IValueConverter).Assembly.GetType("MS.Internal.Data.DefaultValueConverter");
            SystemConvertConverter = typeof(IValueConverter).Assembly.GetType("MS.Internal.Data.SystemConvertConverter");
        }
        static TypeConverter GetConverter(Type targetType)
        {
            var getConverterMethod = Helper.GetStaticMethodHandler<Func<Type, TypeConverter>>(DefaultValueConverter, "GetConverter", BindingFlags.NonPublic | BindingFlags.Static);
            return getConverterMethod(targetType);
        }
        static bool CanConvertUsingSystemConverter(Type sourceType, Type targetType)
        {
            var getConverterMethod = Helper.GetStaticMethodHandler<Func<Type, Type, bool>>(SystemConvertConverter, "CanConvert", BindingFlags.Static | BindingFlags.Public);
            return getConverterMethod(sourceType, targetType);
        }
        static object Convert(Type sourceType, Type targetType, object value)
        {
            TypeConverter typeConverter;
            Type innerType;
            bool canConvertTo, canConvertFrom;
            bool sourceIsNullable = false;
            bool targetIsNullable = false;
            if (sourceType == targetType || targetType == typeof(object) || targetType.IsAssignableFrom(sourceType) || targetType.IsClass && value == null)
                return value;
            if (sourceType != typeof(object))
            {
                if (CanConvertUsingSystemConverter(sourceType, targetType))
                    return System.Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);
                innerType = Nullable.GetUnderlyingType(sourceType);
                if (innerType != null)
                {
                    sourceType = innerType;
                    sourceIsNullable = true;
                }
                innerType = Nullable.GetUnderlyingType(targetType);
                if (innerType != null)
                {
                    targetType = innerType;
                    targetIsNullable = true;
                }
                if (sourceIsNullable || targetIsNullable)
                {
                    return Convert(sourceType, targetType, value);
                }
                typeConverter = GetConverter(sourceType);
                canConvertTo = (typeConverter != null) && typeConverter.CanConvertTo(targetType);
                if (canConvertTo)
                    return typeConverter.ConvertTo(null, CultureInfo.InvariantCulture, value, targetType);
                typeConverter = GetConverter(targetType);
                canConvertFrom = (typeConverter != null) && typeConverter.CanConvertFrom(sourceType);
                if (canConvertFrom)
                    return typeConverter.ConvertFrom(null, CultureInfo.InvariantCulture, value);
            }
            if (targetType == typeof(string))
                return String.Format("{0}", value);
            typeConverter = GetConverter(targetType);
            return typeConverter.CanConvertFrom(sourceType) ? typeConverter.ConvertFrom(null, CultureInfo.InvariantCulture, value) : value;
        }
        public static object GetConvertedValue(Type targetType, object value)
        {
            var sourceType = value == null ? typeof(object) : value.GetType();
            return Convert(sourceType, targetType, value);
        }
        public static object GetConvertedValue(object entity, string property, object value)
        {
            if (value == null)
                return null;
            Type propertyType = null;
            try
            {
                propertyType = Helper.GetPropertyType(entity, property);
            }
            catch (Exception ex)
            {
                ThrowNoProperty(entity, property, ex);
            }
            return GetConvertedValue(propertyType, value);
        }
        public static object GetValue(object entity, string property, BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance)
        {
            Guard.ArgumentNotNull(entity, "entity");
            Guard.ArgumentNotNull(property, "property");
            try
            {
                return Helper.GetPropertyValue(entity, property, bindingFlags);
            }
            catch (Exception ex)
            {
                return ThrowNoProperty(entity, property, ex);
            }
        }
        public static void SetValue(object entity, string property, object value, BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance)
        {
            Guard.ArgumentNotNull(entity, "entity");
            Guard.ArgumentNotNull(property, "property");
            try
            {
                Helper.SetPropertyValue(entity, property, value, bindingFlags);
            }
            catch (Exception ex)
            {
                if (ex is NullReferenceException)
                {
                    ThrowNoProperty(entity, property, ex);
                }
                else
                {
                    throw new NotSupportedException(String.Format("Cannot set property '{0}' to an object {1}", property, entity), ex);
                }
            }
        }
        public static void SetConvertedValue(object entity, string property, object value)
        {
            SetValue(entity, property, GetConvertedValue(Helper.GetPropertyType(entity, property), value));
        }
        public static Type GetPropertyType(object entity, string property, BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance)
        {
            Guard.ArgumentNotNull(entity, "entity");
            Guard.ArgumentNotNull(property, "property");
            return Helper.GetPropertyType(entity, property, bindingFlags);
        }
        static object ThrowNoProperty(object entity, string property, Exception ex)
        {
            throw new ArgumentException(String.Format("Property '{0}' does not exist on object {1}", property, entity), ex);
        }
    }
}
